<?php
declare (strict_types=1);

namespace app\exam\logic;

use app\common\exception\LogicException;
use app\exam\logic\admin\ModuleExamination;
use app\exam\logic\admin\ModuleRollup;
use app\exam\model\PaperConfig;
use app\exam\model\PaperStudent;
use app\course\model\base\{CourseExamLink};
use app\exam\model\PaperAnswer as PaperAnswerModel;
use app\user\logic\teacher\Teacher;
use ArrayObject;
use Exception;
use think\Model;

/**
 * 考试答案逻辑
 * @method group(string $string)
 */
class PaperAnswer extends PaperAnswerModel
{

    /**
     * 获取用户考试记录列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-20
     *
     * @param array $param [description]
     * @return mixed [type]        [description]
     */
    public function getUserExamRecordList(array $param)
    {
        // 数据验证
        $this->getValidate()->scene('list')->failException()->check($param);
        // 参数
        $type = intval($param['type']);
        $mid  = request()->mid;
        $with = [
            'paper_title',
            'paper_visible_answer',
            'rollup_title',
            'course_title',
        ];
        // 数据条件
        switch ($type) {
            case 4:
                $builder = $this->where('rollup_id', '<>', 0);
                break;
            case 5:
                $builder = $this->where('course_id', '<>', 0);
                break;
            case 6:
                $builder = $this->where('rollup_id', 0)->where('course_id', 0)->where('is_assign', '=', 1);
                break;

            default:
                $builder = $this->where('rollup_id', 0)->where('course_id', 0);
                break;
        }
        // 数据
        return $builder->with($with)->whereUserId($mid)->order('commit_time DESC')->paginate(static::getListRows())->each(function ($i) use ($type, $mid) {
            if ($i->course_id) {
                $pass_score = app(CourseExamLink::class)->wherePaperId($i->paper_id)->whereCourseId($i->course_id)->value('pass_score');
                $i->pass    = $i->user_score < $pass_score ? 0 : 1;
            }
            if ($type == 3) {
                // 考试配置
                $module_id = app(ModuleExamination::class)->whereId($i->paper_id)->value('module_id') ?? 0;
                if ($module_id > 0) {
                    $config     = app(PaperConfig::class)->where('data_key', 'examination_' . $module_id)->where('paper_id', $i->paper_id)->field('cnf_data')->findOrEmpty();
                    $pass_score = $config['cnf_data']['exam_score_pass'] ?? 0;
                    $i->pass    = $i->user_score < $pass_score ? 0 : 1;
                } else {
                    $i->pass = 1;
                }
            } else if ($type == 4) {
                // 考试配置
                $module_id = app(ModuleRollup::class)->whereId($i->rollup_id)->value('module_id') ?? 0;
                if ($module_id > 0) {
                    $config     = app(PaperConfig::class)->where('data_key', 'rollup_' . $module_id . '_' . $i->rollup_id)->where('paper_id', $i->paper_id)->field('cnf_data')->findOrEmpty();
                    $pass_score = $config['cnf_data']['exam_score_pass'] ?? 0;
                    $i->pass    = $i->user_score < $pass_score ? 0 : 1;
                } else {
                    $i->pass = 1;
                }
            } else if ($type == 6) {
                // 考试配置
                $pass_score = app(PaperStudent::class)->where('user_id', $mid)->where('exam_status', 0)->value('exam_score_pass') ?? 0;
                $i->pass    = $i->user_score < $pass_score ? 0 : 1;
            }
        })->hidden(['user_id', 'paper_parts', 'answer_data']);
    }

    /**
     * 获取用户指定试卷考试记录 - 基础信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-20
     *
     * @param array $param [description]
     * @return PaperAnswer|array|Model [type]        [description]
     */
    public function getUserExamRecordInfo(array $param)
    {
        // 验证数据
        $this->getValidate()->scene('record')->failException()->check($param);
        // 参数
        $id = intval($param['record_id']);
        // 通过考试记录ID获取试卷考试记录
        $data = $this->getUserExamRecordById($id);
        //
        request()->device_type != 'pc' && $data->paper_parts = $this->parseRecordPaperParts($id, $data->paper_parts);

        return $data;
    }

    /**
     * 获取用户指定试卷考试记录错题解析
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param array $param [description]
     * @return PaperAnswer|array|Model [type]        [description]
     */
    public function getUserExamWrongRecord(array $param)
    {
        // 验证数据
        $this->getValidate()->scene('record')->failException()->check($param);
        // 参数
        $id = intval($param['record_id']);
        //
        $data = $this->getUserExamRecordById($id);

        // $redis 		= Cache::store('redis')->handler();
        // $redis_key 	= request()->device_type == 'pc' ? 'paper_wrong_record' : 'paper_wrong_record_base';
        // $parts 		= $redis->hGet($redis_key, $data->unique_code . '-' . $id);

        // if (!$parts) {
        // 获取试卷错题数据，不包含未答
        $data->paper_parts = $this->getPartsWrongTopicReal($data->paper_parts);
        $data->topic_count = $this->_countPartsTopicNum($data->paper_parts);

        if (request()->device_type != 'pc') {
            $data->paper_parts = $this->parseRecordPaperParts($id, $data->paper_parts);
        }
        // redis缓存
        // $redis->hSet($redis_key, $data->unique_code . '-' . $id, serialize($data->paper_parts));
        // } else {
        // 	$data->paper_parts = unserialize($parts);
        // }
        $data->total_count = $data->topic_count;

        return $data;
    }

    /**
     * 通过考试记录ID获取试卷考试记录
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param int $id [description]
     * @return PaperAnswer|array|Model [type]     [description]
     */
    public function getUserExamRecordById(int $id)
    {
        $data              = $this->with(['paper_title'])->findOrFail($id)->hidden(['answer_data']);
        $data->paper_parts = $this->_handlePartTopicCollected($data->paper_parts);
        $data->save();    // 处理收藏后保存
        $data->paper_parts = $this->_handlePartUnanswer($data->paper_parts);
        $data->total_count = $data->topic_count;

        return $data;
    }

    /**
     * 考试记录收藏
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-07
     *
     * @param  [type] $parts [description]
     * @return mixed [type]        [description]
     */
    private function _handlePartTopicCollected($parts)
    {

        foreach ($parts as &$part) {
            foreach ($part['value'] as &$topic) {
                $topic['collected'] = app(TopicCollect::class)->hasUserCollectedTopic($topic['id']);
            }
        }
        return $parts;
    }

    /**
     * 处理未答
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-05-07
     *
     * @param  [type] $parts [description]
     * @return mixed [type]        [description]
     */
    private function _handlePartUnanswer($parts)
    {
        foreach ($parts as &$part) {
            foreach ($part['value'] as &$topic) {
                switch ($topic['topic_level']) {
                    case 2:
                        $answered = 0;
                        foreach ($topic['topics'] as &$sub) {
                            $sub['answered'] = $sub['answer_data'] ? 1 : 0;

                            if ($sub['answer_data']) $answered = 1;
                        }
                        $topic['answered'] = $answered;
                        break;

                    default:
                        $topic['answered'] = $topic['answer_data'] ? 1 : 0;
                        break;
                }
            }
        }
        return $parts;
    }

    /**
     * 获取试卷错题数据，包含未答
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param  [type] $parts [description]
     * @return array [type]        [description]
     */
    public function getPartsWrongTopic($parts): array
    {
        foreach ($parts as $pk => &$part) {
            foreach ($part['value'] as $key => &$value) {
                if ($value['answer_right']) unset($part['value'][$key]);
                if (2 == $value['topic_level']) {
                    foreach ($value['topics'] as $k => $sub) {
                        if ($sub['answer_right']) unset($value['topics'][$k]);
                    }
                    $value['topics'] = array_values($value['topics']);
                }
            }
            if (!count($part['value'])) {
                unset($parts[$pk]);
            } else {
                $part['value'] = array_values($part['value']);
            }
            unset($part);
        }
        return array_values($parts);
    }

    /**
     * 获取试卷错题数据，不包含未答
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param  [type] $parts [description]
     * @return array [type]        [description]
     */
    public function getPartsWrongTopicReal($parts): array
    {

        foreach ($parts as $pk => &$part) {
            foreach ($part['value'] as $key => $value) {
                if ($value['answer_right'] || !$value['answered']) unset($part['value'][$key]);
            }
            if (!count($part['value'])) {
                unset($parts[$pk]);
            } else {
                $part['value'] = array_values($part['value']);
            }
            unset($part);
        }
        return array_values($parts);
    }

    /**
     * 统计试卷部分的试题数
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-07-13
     *
     * @param  [type] $parts [description]
     * @return int [type]        [description]
     */
    private function _countPartsTopicNum($parts): int
    {
        $num = 0;
        foreach ($parts as $part) {
            $num += count($part['value']);
        }
        return $num;
    }

    /**
     * 获取试卷错题数据，只包含选择题和判断题
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param  [type] $parts [description]
     * @return array [type]        [description]
     */
    public function getPartsWrongTopicPure($parts): array
    {
        foreach ($parts as $pk => &$part) {
            foreach ($part['value'] as $key => $value) {
                if (in_array($value['question_type'], [5, 6, 8])) {
                    unset($part['value'][$key]);
                }
            }
            if (!count($part['value'])) {
                unset($parts[$pk]);
            } else {
                $part['value'] = array_values($part['value']);
            }
            unset($part);
        }
        return array_values($parts);
    }

    /**
     * 解析考试记录paper_parts
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param int   $id
     * @param array $parts [description]
     * @return array [type]              [description]
     */
    public function parseRecordPaperParts(int $id, array $parts): array
    {
        $new_parts = [];

        foreach ($parts as $part) {
            $vals = [];
            foreach ($part['value'] as $value) {
                $val['epar_id']      = $id;
                $val['part_id']      = $part['id'];
                $val['topic_id']     = $value['topic_id'];
                $val['topic_level']  = $value['topic_level'];
                $val['answer_right'] = $value['answer_right'];
                $val['answered']     = $value['answered'];
                $val['user_score']   = $value['user_score'];
                $val['subjective']   = $value['subjective'];

                if (2 == $value['topic_level']) {
                    $subs = [];
                    foreach ($value['topics'] as $sub) {
                        $subs[] = [
                            'topic_id'     => $sub['id'],
                            'answer_right' => $sub['answer_right'],
                            'answered'     => $sub['answer_data'] ? 1 : 0,
                            'user_score'   => $sub['user_score'],
                            'subjective'   => $sub['subjective'],
                        ];
                    }
                    $val['topics'] = $subs;
                }

                $vals[] = $val;
                unset($val);
            }
            $part['value'] = $vals;
            $new_parts[]   = $part;
        }

        return $new_parts;
    }

    /**
     * 获取用户指定试卷试题记录
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-22
     *
     * @param array $param [description]
     * @return array|ArrayObject [type]        [description]
     */
    public function getTopicAnswerRecord(array $param)
    {
        // 验证数据
        $this->getValidate()->scene('topic')->failException()->check($param);
        // 参数
        $unique_code = $param['unique_code'];
        $epar_id     = intval($param['epar_id']);
        $part_id     = intval($param['part_id']);
        $topic_id    = intval($param['topic_id']);
        // redis缓存
        // $redis = Cache::store('redis')->handler();
        // $parts = $redis->hGet('paper_parts_unique_handled', $unique_code . '-' . $epar_id);
        // if (!$parts) {
        //
        $data  = $this->findOrEmpty($epar_id);
        $parts = $this->_handlePartUnanswer($data->paper_parts);
        // $redis->hSet('paper_parts_unique_handled', $unique_code . '-' . $epar_id, serialize($parts));
        // } else {
        // 	$parts = unserialize($parts);
        // }

        return $this->returnTopicPrevNext($unique_code, $epar_id, $part_id, $topic_id, $parts);

    }

    /**
     * 错题重练获取试题
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param array $param [description]
     * @return array|ArrayObject [type]        [description]
     */
    public function getWrongTopicWithAdjacent(array $param)
    {
        // 验证数据
        $this->getValidate()->scene('topic')->failException()->check($param);
        // 参数
        $unique_code = $param['unique_code'];
        $epar_id     = intval($param['epar_id']);
        $part_id     = intval($param['part_id']);
        $topic_id    = intval($param['topic_id']);
        // redis缓存
        // $redis 		= Cache::store('redis')->handler();
        // $redis_key 	= request()->device_type == 'pc' ? 'paper_wrong_record' : 'paper_wrong_record_base';
        // $parts 		= $redis->hGet($redis_key, $unique_code . '-' . $epar_id);

        // if (!$parts) {
        // 通过考试记录ID获取试卷考试记录
        $data = $this->getUserExamRecordById($epar_id);
        // 获取试卷错题数据，不包含未答
        $parts = $this->getPartsWrongTopicReal($data->paper_parts);
        // 错题重练，剔除材料题，填空题，解答题
        $parts = $this->getPartsWrongTopicPure($parts);
        // redis缓存
        // $redis->hSet($redis_key, $data->unique_code . '-' . $epar_id, serialize($parts));
        // } else {
        // 	$parts = unserialize($parts);
        // }

        return $this->returnTopicPrevNext($unique_code, $epar_id, $part_id, $topic_id, $parts);
    }

    /**
     * 获取试题，并返回上一题、下一题数据
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-23
     *
     * @param $unique_code
     * @param $epar_id
     * @param $part_id
     * @param $topic_id
     * @param $parts
     * @return array|ArrayObject [type]              [description]
     */
    public function returnTopicPrevNext($unique_code, $epar_id, $part_id, $topic_id, $parts)
    {
        $part_num = count($parts);
        if (!$part_num) return new ArrayObject();

        $part_key = $part_id ? array_search($part_id, array_column($parts, 'id')) : 0;
        $part_id  = $parts[$part_key]['id'];
        $part     = $parts[$part_key]['value'];
        if (!$part) return new ArrayObject();

        $topic_num = count($part);
        $topic_key = $topic_id ? array_search($topic_id, array_column($part, 'id')) : 0;
        $topic     = $part[$topic_key];
        // 试题收藏状态
        $topic['collected'] = app(TopicCollect::class)->hasUserCollectedTopic($topic['id']);

        if (request()->device_type != 'pc') $parts = $this->parseRecordPaperParts($epar_id, $parts);

        if ($part_key == 0 && $topic_key == 0) {
            $prev_part_id  = null;
            $prev_topic_id = null;
        } else if ($topic_key == 0 && $part_key != 0) {
            $prev_part_id  = $parts[$part_key - 1]['id'];
            $_part         = $parts[$part_key - 1]['value'];
            $prev_topic_id = $_part[count($_part) - 1]['topic_id'];
        } else {
            $prev_part_id  = $part_id;
            $prev_topic_id = $part[$topic_key - 1]['topic_id'];
        }

        $next_part_id  = $topic_key == $topic_num - 1 ? ($part_key == $part_num - 1 ? null : $parts[$part_key + 1]['id']) : $part_id;
        $next_topic_id = $topic_key == $topic_num - 1 ? ($part_key == $part_num - 1 ? null : $parts[$part_key + 1]['value'][0]['topic_id']) : $part[$topic_key + 1]['topic_id'];

        $prev = [
            'epar_id'     => $epar_id,
            'unique_code' => $unique_code,
            'part_id'     => $prev_part_id,
            'topic_id'    => $prev_topic_id,
        ];
        $next = [
            'epar_id'     => $epar_id,
            'unique_code' => $unique_code,
            'part_id'     => $next_part_id,
            'topic_id'    => $next_topic_id,
        ];

        return ['topic' => $topic, 'prev' => $prev, 'next' => $next];
    }

    /**
     * 获取待/已批阅试卷列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-03
     *
     * @param $type
     * @return mixed [type] [description]
     */
    public function getPapers($type)
    {
        $status = $type == 'uncorrected' ? 1 : 2;
        // 检查讲师身份
        $teacher = app(Teacher::class)->isTeacher(request()->mid, true);
        if (!$teacher) throw new LogicException('您不是讲师，无法操作');

        return app(Paper::class)->alias('p')->join('exam_paper_answer_record r', "r.paper_id=p.id AND r.rollup_id=0 AND r.answer_status=$status AND subjective=1")->field('p.id,p.title')->whereReviewer($teacher)->group('r.paper_id')->paginate(static::getListRows())->each(function ($i) {
            $i->user_count = $this->wherePaperId($i->id)->whereRollupId(0)->group('user_id')->count('user_id');
        });
    }

    /**
     * 获取试卷用户列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-03
     *
     * @param array $param
     * @return mixed [type] [description]
     */
    public function getPaperUsers(array $param)
    {
        // 参数验证
        $this->getValidate()->scene('paper_users')->failException()->check($param);
        // 参数
        $status   = $param['status'];
        $paper_id = $param['paper_id'];
        // 检查讲师身份
        $teacher = app(Teacher::class)->isTeacher(request()->mid, true);
        if (!$teacher) throw new LogicException('您不是讲师，无法操作');
        $yes = app(Paper::class)->whereId($paper_id)->whereReviewer($teacher)->findOrEmpty();
        if ($yes->isEmpty()) throw new LogicException('您不是阅卷讲师，无法操作');

        return $this->with(['paper_title', 'user_name'])->field('id as record_id,user_id,paper_id')->wherePaperId($paper_id)->whereAnswerStatus($status)->paginate(static::getListRows());
    }

    /**
     * 阅卷渲染页面
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-03
     *
     * @param array $param
     * @return PaperAnswer|array|Model [type] [description]
     */
    public function getPaperUserFetch(array $param)
    {
        // 参数验证
        $this->getValidate()->scene('user_fetch')->failException()->check($param);
        // 参数
        $record_id = $param['record_id'];
        $paper_id  = $param['paper_id'];
        // 检查讲师身份
        $teacher = app(Teacher::class)->isTeacher(request()->mid, true);
        if (!$teacher) throw new LogicException('您不是讲师，无法操作');
        $paper = app(Paper::class)->whereId($paper_id)->whereReviewer($teacher)->findOrEmpty();
        if ($paper->isEmpty()) throw new LogicException('您不是阅卷讲师，无法操作');
        //
        $record              = $this->with(['user_name'])
            ->field('id,user_id,paper_id,unique_code,paper_parts,time_takes,commit_time,answer_status,paper_score,user_score')
            ->findOrFail($record_id);
        $record->paper_title = $paper->title;

        return $record;
    }

    /**
     * 提交阅卷数据
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-04
     *
     * @param array $param
     * @return void [type] [description]
     * @throws Exception
     */
    public function postPaperUserScore(array $param)
    {
        // 参数
        $data      = is_array($param['data']) ? $param['data'] : json_decode($param['data'], true);
        $record_id = intval($param['record_id']);
        if (!is_array($data)) throw new LogicException('数据错误');

        $this->startTrans();
        try {

            $answer_data = $this->findOrEmpty($record_id);
            if ($answer_data->isEmpty()) throw new LogicException('数据错误.');
            // paper_parts键值重组
            $parts = $this->kvalsParts($answer_data->paper_parts);
            // 处理答案
            $score_add = 0;

            foreach ($data as $v) {
                $score        = $v['score'];
                $v['epar_id'] = $record_id;
                unset($v['score']);

                $option = app(PaperAnswerOption::class)->where($v)->findOrEmpty();
                if (!$option->isEmpty()) {

                    if ($option->epar_id != $record_id) throw new LogicException('数据错误..');
                    $answer_right = $option->topic_score == $score ? 1 : 0;
                    $score_change = $score - $option->user_score;

                    $option->answer_right = $answer_right;
                    $option->user_score   = $score;

                    // 累积阅卷分总变化得分
                    $score_add += $score_change;

                    if ($v['topic_level'] == 1) {

                        $key = $v['topic_id'] . '-' . $v['topic_level'];

                        if (isset($parts[$v['part_id']]['value'][$key])) {
                            $parts[$v['part_id']]['value'][$key]['answer_right'] = $answer_right;
                            $parts[$v['part_id']]['value'][$key]['user_score']   = $score;
                        }
                    } else if ($v['topic_level'] == 2) {
                        // 大题ID
                        $topic_id = app(TopicMaterial::class)->whereId($v['topic_id'])->value('topic_id');
                        $key      = $topic_id . '-' . $v['topic_level'];

                        if ($topic_id && isset($parts[$v['part_id']]['value'][$key])) {

                            $parts[$v['part_id']]['value'][$key]['topics'][$v['topic_id']]['answer_right'] = $answer_right;
                            $parts[$v['part_id']]['value'][$key]['topics'][$v['topic_id']]['user_score']   = $score;
                            //
                            $parts[$v['part_id']]['value'][$key]['user_score'] += $score_change;

                        }
                    }

                    if (!$option->save()) throw new LogicException('批阅失败，请重试');
                }
            }

            foreach ($parts as &$part) {
                foreach ($part['value'] as &$value) {
                    if (isset($value['topics'])) {
                        $value['topics'] = array_values($value['topics']);
                        unset($value);
                    }
                }

                $part['value'] = array_values($part['value']);
                unset($part);
            }

            $answer_data->paper_parts   = array_values($parts);
            $answer_data->user_score    += $score_add;
            $answer_data->answer_status = 2;
            if (!$answer_data->save()) throw new LogicException('批阅失败，请重试.');

            $this->commit();
        } catch (LogicException|Exception $e) {
            $this->rollback();
            throw new LogicException($e->getMessage());
        }
    }

    /**
     * 重组paper_part,
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-06-21
     *
     * @param  [type] $paper_parts [description]
     * @return array [type]              [description]
     */
    public function kvalsParts($paper_parts): array
    {

        $parts = [];
        foreach ($paper_parts as $part) {
            $np = [];
            foreach ($part['value'] as $tv) {

                if ($tv['topic_level'] == 2 && $tv['topics']) {
                    foreach ($tv['topics'] as $k => $t) {
                        $tv['topics'][$t['id']] = $t;
                        unset($tv['topics'][$k]);
                    }
                }

                $np[$tv['id'] . '-' . $tv['topic_level']] = $tv;
            }
            $part['value']      = $np;
            $parts[$part['id']] = $part;
        }

        return $parts;
    }

    /**
     * 获取考试次数
     *
     * @param int   $paperId
     * @param int   $userId
     * @param array $where
     * @return int
     */
    public function getUserAnswerNumber(int $paperId,int $userId,array $where = [])
    {
        return  $this->where('paper_id',$paperId)->where('user_id',$userId)->where($where)->count();
    }
}
